using System.Collections.Generic;
using System.Diagnostics;
using rsync;

namespace rsync
{
    internal class Sync
    {
        private readonly uint _inputSize;
        private readonly Device[] _devices;

        private TimeSync _timeSync;
        private Input[] _syncInputs;
        private StateStorage _stateStorage;

        public Sync(Device[] devices, uint inputSize)
        {
            _devices = devices;
            _inputSize = inputSize;
            _timeSync = new TimeSync();
            _stateStorage = new StateStorage(TimeSync.MaxRollbackFrames);
            _syncInputs = new Input[_devices.Length];
        }

        /// <summary>
        /// 添加一个远程终端的输入
        /// </summary>
        /// <param name="deviceId">远程终端ID</param>
        /// <param name="frame">远程终端帧ID</param>
        /// <param name="deviceInput">远程中断掉恶输入数据</param>
        public void AddRemoteInput(uint deviceId, int frame, byte[] deviceInput)
        {
            // 只允许向本地设备添加输入
            Debug.Assert(_devices[deviceId].Type == Device.DeviceType.Remote);
            // 如果需要，更新设备变量
            if (_devices[deviceId].RemoteFrame < frame)
            {
                _devices[deviceId].RemoteFrame = frame;
                _devices[deviceId].RemoteFrameAdvantage = _timeSync.LocalFrame - frame;
                // 让他们知道你收到了包裹
                _devices[deviceId].SendMessage(new DeviceInputAckMessage { Frame = (uint)frame });
            }
            AddDeviceInput(frame, deviceId, deviceInput);
        }

        public uint FramesAhead()
        {
            return (uint)_timeSync.LocalFrameAdvantage;
        }

        /// <summary>
        /// 设置本地终端，用于区分远程和本地
        /// </summary>
        /// <param name="deviceId">终端ID</param>
        /// <param name="playerCount">玩家数量</param>
        /// <param name="frameDelay">帧延迟量</param>
        public void SetLocalDevice(uint deviceId, uint playerCount, uint frameDelay)
        {
            _syncInputs[deviceId] = new Input(_inputSize, playerCount, frameDelay);
        }

        /// <summary>
        /// 添加一个远程终端
        /// </summary>
        /// <param name="deviceId">该远程终端ID</param>
        /// <param name="playerCount">该远程终端的玩家数量</param>
        public void AddRemoteDevice(uint deviceId, uint playerCount)
        {
            _syncInputs[deviceId] = new Input(_inputSize, playerCount);
        }

        /// <summary>
        /// 向前推进帧
        /// </summary>
        /// <param name="localDeviceId">本地终端ID</param>
        /// <param name="deviceInput">本机输入</param>
        /// <returns></returns>
        public List<SessionAction> AdvanceSync(uint localDeviceId, byte[] deviceInput)
        {
            // 应在轮询远程设备以获取其消息后调用。
            Debug.Assert(deviceInput != null);

            bool isTimeSynced = _timeSync.IsTimeSynced(_devices);

            UpdateSyncFrame();

            var actions = new List<SessionAction>();

            // 在initialFrame上创建savestate以支持回滚到它
            //例如，如果initframe = 0，那么0将是第一个保存回滚的选项。
            if (_timeSync.LocalFrame == TimeSync.InitialFrame)
            {
                actions.Add(new SessionSaveGameAction(_timeSync.LocalFrame, _stateStorage));
            }

            // 回滚更新
            if (_timeSync.ShouldRollback())
            {
                actions.Add(new SessionLoadGameAction(_timeSync.SyncFrame, _stateStorage));
                for (int i = _timeSync.SyncFrame + 1; i <= _timeSync.LocalFrame; i++)
                {
                    actions.Add(new SessionAdvanceFrameAction(i, GetFrameInput(i).Inputs));
                    actions.Add(new SessionSaveGameAction(i, _stateStorage));
                }
            }

            if (isTimeSynced)
            {
                _timeSync.LocalFrame++;

                AddLocalInput(localDeviceId, deviceInput);
                SendLocalInputs(localDeviceId);

                actions.Add(new SessionAdvanceFrameAction(_timeSync.LocalFrame, GetFrameInput(_timeSync.LocalFrame).Inputs));
                actions.Add(new SessionSaveGameAction(_timeSync.LocalFrame, _stateStorage));   
            }

            return actions;
        }

        /// <summary>
        /// 发送本机的输入给其他终端
        /// </summary>
        /// <param name="localDeviceId">本地终端ID</param>
        private void SendLocalInputs(uint localDeviceId)
        {
            foreach (var device in _devices)
            {
                if (device.Type == Device.DeviceType.Remote)
                {
                    //为起始帧使用某种固定值来补偿数据包丢失
//8是个神奇的数字……待办事项:将其替换为更优化的东西
                    uint startingFrame = _timeSync.LocalFrame <= 8 ? 0 : (uint)_timeSync.LocalFrame - 8;
                    uint finalFrame = (uint)(_timeSync.LocalFrame + _syncInputs[localDeviceId].GetFrameDelay());

                    var combinedInput = new List<byte>();

                    for (uint i = startingFrame; i <= finalFrame; i++)
                    {
                        combinedInput.AddRange(GetDeviceInput((int)i, localDeviceId).Inputs);
                    }

                    device.SendMessage(new DeviceInputMessage
                    {
                        StartFrame = startingFrame,
                        EndFrame = finalFrame,
                        Input = combinedInput.ToArray()
                    });
                }
            }
        }

        /// <summary>
        /// 更新同步帧
        /// </summary>
        private void UpdateSyncFrame()
        {
            int finalFrame = _timeSync.RemoteFrame;
            if (_timeSync.RemoteFrame > _timeSync.LocalFrame)
            {
                finalFrame = _timeSync.LocalFrame;
            }
            bool foundMistake = false;
            int foundFrame = finalFrame;
            for (int i = _timeSync.SyncFrame + 1; i <= finalFrame; i++)
            {
                foreach (var input in _syncInputs)
                {
                    var predInput = input.GetPredictedInput(i);
                    if (predInput.Frame == i &&
                        input.GetInput(i, false).Frame == i)
                    {
                        // 错误的预测
                        if (!predInput.Equal(input.GetInput(i, false), true))
                        {
                            foundFrame = i - 1;
                            foundMistake = true;
                        }
                        // 删除预测表单队列
                        input.ResetPrediction(i);
                    }
                }
                if (foundMistake) break;
            }
            _timeSync.SyncFrame = foundFrame;
        }

        /// <summary>
        /// 添加本机的输入
        /// </summary>
        /// <param name="deviceId">终端ID</param>
        /// <param name="deviceInput">输入的数据</param>
        private void AddLocalInput(uint deviceId, byte[] deviceInput)
        {
            // 只允许向本地设备添加输入
            Debug.Assert(_devices[deviceId].Type == Device.DeviceType.Local);
            AddDeviceInput(_timeSync.LocalFrame, deviceId, deviceInput);
        }
        
        /// <summary>
        /// 添加指定帧数制定终端的输入数据
        /// </summary>
        /// <param name="frame">帧ID</param>
        /// <param name="deviceId">终端ID</param>
        /// <param name="deviceInput">该终端的输入数据</param>
        private void AddDeviceInput(int frame, uint deviceId, byte[] deviceInput)
        {
            Debug.Assert(deviceInput.Length == _devices[deviceId].PlayerCount * _inputSize,
             "the length of the given deviceInput isnt correct!");

            var input = new InputData(frame, _inputSize, _devices[deviceId].PlayerCount);
            input.SetInputs(0, _devices[deviceId].PlayerCount, deviceInput);

            _syncInputs[deviceId].AddInput(frame, input);
        }

        /// <summary>
        /// 获取指定帧指定终端的输入
        /// </summary>
        /// <param name="frame">帧ID</param>
        /// <param name="deviceId">终端ID</param>
        /// <returns></returns>
        private InputData GetDeviceInput(int frame, uint deviceId)
        {
            return _syncInputs[deviceId].GetInput(frame);
        }

        /// <summary>
        /// 获取指定帧所有玩家输入
        /// </summary>
        /// <param name="frame">帧ID</param>
        /// <returns></returns>
        public InputData GetFrameInput(int frame)
        {
            uint playerCount = 0;
            foreach (var device in _devices)
            {
                playerCount += device.PlayerCount;
            }
            // 将所有设备输入添加到单个GameInput中
            var input = new InputData(frame, _inputSize, playerCount);
            // 偏移量需要将玩家的输入放到正确的位置
            uint playerOffset = 0;
            for (uint i = 0; i < _devices.Length; i++)
            {
                // 获取设备的输入并将其添加到其他输入中
                var tmpInput = GetDeviceInput(frame, i);
                input.SetInputs(playerOffset, _devices[i].PlayerCount, tmpInput.Inputs);
                // 将播放器偏移量移动到下一个设备的位置
                playerOffset += _devices[i].PlayerCount;
            }
            return input;
        }

        /// <summary>
        /// 当前终端的帧ID
        /// </summary>
        /// <returns></returns>
        public int Frame() => _timeSync.LocalFrame;
        
        /// <summary>
        /// 当前终端预测推进的超前帧数
        /// </summary>
        /// <returns></returns>
        public int FrameAdvantage() => _timeSync.LocalFrameAdvantage;
        
        /// <summary>
        /// 终端需要回滚的帧数
        /// </summary>
        /// <returns></returns>
        public int RollbackFrames() => _timeSync.LocalFrame - (_timeSync.SyncFrame + 1);
    }
}